package org.dval.plugin.ioc;

import com.jfinal.log.Log;
import org.dval.annotation.Entity;
import org.dval.annotation.Inject;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Bean注入工具类
 */
public class InjectUtils {
    private static Log log = Log.getLog(InjectUtils.class);

    public static void inject(final Object target) {
        Class<?> clazz = target.getClass();
        inject(clazz, target);
    }

    public static void inject(Class<?> clazz, final Object target) {
        //Field[] fields = clazz.getDeclaredFields();
        List<Field> fields = IocKit.getFields(clazz);
        List<Field> fieldNows = new ArrayList<>();
        boolean isNow = false;
        if (fields == null || fields.isEmpty()) {
            isNow = true;
            fields = Arrays.asList(clazz.getDeclaredFields());
        }
        for (Field field : fields) {
            if (null == field || !field.isAnnotationPresent(Inject.class)) {
                continue;
            }

            int modifiers = field.getModifiers();
            if (Modifier.isFinal(modifiers)) {
                throw new RuntimeException("Class: " + clazz + " field: " + field.getName() + " is final!");
            }
            if (Modifier.isStatic(modifiers)) {
                log.warn("Class " + clazz + " field " + field.getName() + "is static!");
            }
            if (isNow) {
                fieldNows.add(field);
            }
            Object value = IocKit.getBean(field.getType());
            if (value == null) {
                throw new RuntimeException("not has bean:" + field.getType());
            }
            if (!field.isAccessible()) {
                field.setAccessible(true);
            }
            try {
                field.set(target, value);
            } catch (IllegalArgumentException e) {
                log.error(e.getMessage(), e);
                throw new RuntimeException(e);
            } catch (IllegalAccessException e) {
                log.error(e.getMessage(), e);
                throw new RuntimeException(e);
            }
        }
        genericInjection(clazz, target);
        IocKit.saveBean(clazz, fieldNows);
    }

    private static void genericInjection(Class<?> clazz, Object target) {
        try {
            if (clazz.getAnnotation(Entity.class) != null) {
                return;
            }
            Type type = clazz.getGenericSuperclass();
            if (type == null || type == Object.class) {
                return;
            }
            if (!(type instanceof ParameterizedType)) {
                List<Field> fields = Arrays.asList(clazz.getSuperclass().getDeclaredFields());
                for (Field field : fields) {
                    if (null == field || !field.isAnnotationPresent(Inject.class)) {
                        continue;
                    }
                    field.setAccessible(true);
                    field.set(target, IocKit.getBean(field.getType()));
                }
                return;
            }
            ParameterizedType o = ((ParameterizedType) type);
            Type[] parameterizedType = o.getActualTypeArguments();
            do {
                List<Field> fields = Arrays.asList(clazz.getSuperclass().getDeclaredFields());
                field:
                for (Field field : fields) {
                    field.setAccessible(true);
                    for (Type generic : parameterizedType) {
                        Class<?> claz = (Class<?>) generic;
                        if (field.getType().isAssignableFrom(claz)) {
                            field.set(target, IocKit.getBean(claz));
                            continue field;
                        }
                    }
                }
                clazz = clazz.getSuperclass();
            } while (clazz.getSuperclass() != null && clazz.getSuperclass() != Object.class);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}